﻿using LibUsbDotNet;
using LibUsbDotNet.Main;

namespace GCC.Container.Abstracts
{
    /// <summary>
    /// 抽象的USB通信客户端
    /// </summary>
    /// <typeparam name="T">通信数据类型</typeparam>
    /// <remarks>注意,由于串口得特殊性,不关析构了服务还是client,双方都将被联合析构,想要停止只能析构</remarks>
    public abstract class AbsCommUSBClient<T> : AbsCommClient, IDisposable
    {
        /// <summary>
        /// 是否已经销毁
        /// </summary>
        protected bool _disposed;

        /// <summary>
        /// 日志对象
        /// </summary>
        protected readonly AbsGCCLogger _logger;

        /// <summary>
        /// 头长度
        /// </summary>
        protected int _headSize = 4;

        /// <summary>
        /// 取消token
        /// </summary>
        protected readonly CancellationTokenSource _cancellToken = new();

        /// <summary>
        /// usb对象
        /// </summary>
        protected UsbDevice? _usbDevice;

        /// <summary>
        /// 串口对象
        /// </summary>
        protected UsbEndpointReader? _usbDeviceReader;

        /// <summary>
        /// 串口对象
        /// </summary>
        protected UsbEndpointWriter? _usbDeviceWriter;

        /// <summary>
        /// 销毁事件
        /// </summary>
        internal event Action? OnDisposing;

        /// <summary>
        /// 析构函数
        /// </summary>
        ~AbsCommUSBClient()
        {
            Dispose(false);
        }

        /// <summary>
        /// 销毁模型
        /// </summary>
        /// <param name="flag">是否是用户主动调用</param>
        protected virtual void Dispose(bool flag)
        {
            if (_disposed)
            {
                return;
            }
            _disposed = true;


            try
            {
                OnDisposing?.Invoke();
                _cancellToken.Cancel();
                _cancellToken.Dispose();
                if (_usbDevice is not null && _usbDevice.IsOpen)
                {
                    IUsbDevice? wholeUsbDevice = _usbDevice as IUsbDevice;
                    if (wholeUsbDevice is not null)
                    {
                        _ = wholeUsbDevice.ReleaseInterface(0);
                    }
                    _ = _usbDevice.Close();
                }
                UsbDevice.Exit();
            }
            catch (Exception ex)
            {
                _logger.Error("AbsCommTcpClient dispose error.", exception: ex);
            }
        }

        /// <summary>
        /// 销毁模型
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger">日志模块实例</param>
        /// <param name="name">名称</param>
        public AbsCommUSBClient(string name, AbsGCCLogger logger)
        {
            ServerType = "COM";
            _logger = logger;
            Name = name;
        }

        /// <summary>
        /// 绑定串口信息
        /// </summary>         
        internal void BindTo(int pid, int vid, ReadEndpointID readEndPoint, WriteEndpointID writeEndPoint)
        {

            UsbDeviceFinder MyUsbFinder = new(pid, vid);
            _usbDevice = UsbDevice.OpenUsbDevice(MyUsbFinder);
            if (_usbDevice is null)
            {
                return;
            }
            IUsbDevice? wholeUsbDevice = _usbDevice as IUsbDevice;
            if (wholeUsbDevice is not null)
            {
                _ = wholeUsbDevice.SetConfiguration(1);
                _ = wholeUsbDevice.ClaimInterface(0);
            }
            _usbDeviceReader = _usbDevice.OpenEndpointReader(readEndPoint);
            _usbDeviceWriter = _usbDevice.OpenEndpointWriter(writeEndPoint);

        }

        /// <summary>
        /// 读写一次
        /// </summary>
        /// <param name="inData">读数据</param>
        /// <param name="outData">出数据</param>
        /// <returns></returns>
        public bool ReadWrite(T inData, out T? outData)
        {
            outData = default;
            if (_usbDeviceReader is null || _usbDeviceWriter is null)
            {
                _logger.Error($"Please bind usb port first.");
                return false;
            }
            try
            {

                int totalLen = _headSize + GetBodyLengh(inData);

                var allDataBuffer = new byte[totalLen].AsMemory();
                PutHeader(allDataBuffer[.._headSize], inData);
                PutData(allDataBuffer[..(_headSize)], allDataBuffer[(_headSize)..], inData);

                var ec = _usbDeviceWriter.Write(allDataBuffer, 2000, out int bytesWritten);
                if (ec != ErrorCode.None)
                {
                    _logger.Error($"Read write error:{UsbDevice.LastErrorString}");
                    return false;
                }
                if (bytesWritten != totalLen)
                {
                    _logger.Error($"Read write error:bytesWritten != totalLen");
                    return false;
                }
                byte[]? headBuffer = null;

                if (_headSize != 0)
                {
                    headBuffer = new byte[_headSize];
                }
                var headReadCount = 0;
                var bodyReadCount = 0;
                while (ec == ErrorCode.None)
                {

                    var bodyLength = 0;
                    if (headBuffer is not null)
                    {
                        while (headReadCount < _headSize)
                        {
                            var res = _usbDeviceReader.Read(headBuffer[headReadCount..], 10000, out var bytesRead);
                            if (res != ErrorCode.None)
                            {
                                _logger.Error($"Read usb device error:{UsbDevice.LastErrorString}");
                                continue;
                            }
                            headReadCount += bytesRead;
                        }
                        if (!CheckHeader(headBuffer, out bodyLength))
                        {
                            continue;
                        }
                    }


                    var bodyDataBuffer = new byte[bodyLength];

                    while (bodyReadCount < bodyLength)
                    {
                        var res = _usbDeviceReader.Read(bodyDataBuffer[bodyReadCount..], 10000, out var bytesRead);
                        if (res != ErrorCode.None)
                        {
                            _logger.Error($"Read usb device error:{UsbDevice.LastErrorString}");
                            continue;
                        }
                        bodyReadCount += bytesRead;
                    }
                    outData = HandleData(headBuffer, bodyDataBuffer);
                }
            }
            catch (Exception)
            {

                throw;
            }

            return true;
        }

        /// <summary>
        /// 获取数据的内容长度
        /// </summary>
        /// <param name="data"></param>
        protected abstract int GetBodyLengh(T data);

        /// <summary>
        /// 检查头
        /// </summary>
        /// <param name="headBuffer">头部缓冲</param>
        /// <param name="bodyLength">内容长度</param>
        /// <returns></returns>
        protected abstract bool CheckHeader(in Memory<byte> headBuffer, out int bodyLength);

        /// <summary>
        /// 处理数据
        /// </summary>
        /// <param name="headBuffer">头部buffer</param>
        /// <param name="bodyBuffer">数据buffer</param>
        protected abstract T HandleData(in Memory<byte> headBuffer, in Memory<byte> bodyBuffer);

        /// <summary>
        /// 检查头
        /// </summary>
        /// <param name="headBuffer">头部缓冲</param> 
        /// <param name="data">数据</param>
        /// <returns></returns>
        protected abstract void PutHeader(Memory<byte> headBuffer, in T data);

        /// <summary>
        /// 处理数据
        /// </summary>
        /// <param name="headBuffer">头部buffer</param>
        /// <param name="bodyBuffer">数据buffer</param>
        /// <param name="data">数据</param>
        protected abstract void PutData(in Memory<byte> headBuffer, Memory<byte> bodyBuffer, in T data);

    }
}
